package com.tom.utils;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.text.DateFormat;
import java.util.*;
import java.util.regex.Matcher;

/**
 * 解析脚本sql工具类
 *
 * @author zx
 * @date 2024-11-09 18:22
 */
@Slf4j
public class AnalysisScriptSqlUtils {

    /**
     * 解析脚本SQL
     * @param scriptSql 脚本SQL
     * @param paramMap 参数对象集合
     * @return 解析后的SQL(拼装好参数可执行的SQL)
     */
    public static String analysisDynamicScriptSql(String scriptSql, Map<String,Object> paramMap){
        scriptSql = "<script>"+scriptSql+"</script>";
        log.info("脚本sql:{},参数:{}",scriptSql,paramMap);
        // 创建MyBatis配置对象
        Configuration configuration = new Configuration();

        // 创建LanguageDriver
        LanguageDriver languageDriver = new XMLLanguageDriver();

        // 解析SQL
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, scriptSql, Map.class);
        DynamicSqlSource dynamicSqlSource = (DynamicSqlSource) sqlSource;

        // 执行动态SQL解析
        BoundSql boundSql = dynamicSqlSource.getBoundSql(paramMap);

        // 获取参数对象
        Object parameterObject = boundSql.getParameterObject();

        // 获取参数映射
        List<ParameterMapping> params = boundSql.getParameterMappings();
        // 获取到执行的SQL
        String finalSql = boundSql.getSql();
        log.info("解析动态sql:{}",finalSql);

        if(CollectionUtils.isEmpty(params)&& Objects.isNull(parameterObject)){
            return finalSql;
        }
        finalSql = assembleSqlParam(params,parameterObject,finalSql,configuration,boundSql);
        log.info("填充参数后的SQL：{}",finalSql);
        return finalSql;
    }

    private static String assembleSqlParam(List<ParameterMapping> params,Object parameterObject,String analysisAfterSql,Configuration configuration,BoundSql boundSql ){
        // TypeHandlerRegistry 是 MyBatis 用来管理 TypeHandler 的注册器。TypeHandler 用于在 Java 类型和 JDBC 类型之间进行转换
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        // 如果参数对象的类型有对应的 TypeHandler，则使用 TypeHandler 进行处理
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){
            analysisAfterSql = analysisAfterSql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
        }else {
            // 否则，逐个处理参数映射
            for (ParameterMapping param : params) {
                // 获取参数的属性名
                String propertyName = param.getProperty();
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                // 检查对象中是否存在该属性的 getter 方法，如果存在就取出来进行替换
                if (metaObject.hasGetter(propertyName)){
                    Object obj = metaObject.getValue(propertyName);
                    analysisAfterSql = analysisAfterSql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    // 检查 BoundSql 对象中是否存在附加参数。附加参数可能是在动态 SQL 处理中生成的，有的话就进行替换
                }else if (boundSql.hasAdditionalParameter(propertyName)){
                    Object obj = boundSql.getAdditionalParameter(propertyName);
                    analysisAfterSql = analysisAfterSql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                }else {
                    // 如果都没有，说明SQL匹配不上，带上“缺失”方便找问题
                    analysisAfterSql = analysisAfterSql.replaceFirst("\\?", "缺失");
                }
            }
        }
        return analysisAfterSql;
    }

    private static String getParameterValue(Object object) {
        String value = "";
        if (object instanceof String){
            value = "'" + object.toString() + "'";
        }else if (object instanceof Date){
            DateFormat format = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + format.format((Date) object) + "'";
        } else if (object !=null) {
            value = object.toString();
        }
        return value;
    }


}
